package la.iok.hzsvn.lewin.movie.cloud.service;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import la.iok.hzsvn.lewin.movie.cloud.model.*;
import la.iok.hzsvn.lewin.movie.core.exception.ErrorCode;
import la.iok.hzsvn.share.annotations.NotNull;
import la.iok.hzsvn.share.tools.exception.SystemException;
import la.iok.hzsvn.share.utils.StringUtils;
import org.springframework.cglib.beans.BeanMap;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

@Service
public class WxRestServiceImpl implements WxRestService{
    private static final ExecutorService executorService = Executors.newFixedThreadPool(1);
    private final RestTemplate restTemplate;
    private final WxMiniProperties wxMiniProperties;
    private final ObjectMapper objectMapper;
    private final Map<Integer, AccessTokenRes> accessToken = new HashMap<>();

    public WxRestServiceImpl(RestTemplate restTemplate, WxMiniProperties wxMiniProperties, ObjectMapper objectMapper) {
        this.restTemplate = restTemplate;
        this.wxMiniProperties = wxMiniProperties;
        this.objectMapper = objectMapper;
    }

    @Override
    public UserPhoneNumberRes phoneNumber(@NotNull String code, Integer client) {
        String url = "https://api.weixin.qq.com/wxa/business/getuserphonenumber?access_token={accessToken}";
        AccessTokenRes accessToken = getAccessToken(client, true);
        String content = restTemplate.postForObject(url,String.format("{\"code\":\"%s\"}",code),String.class,accessToken.getAccessToken());
        ErrorCode.WX_INVOKER_ERROR.assertNotBlank(content,"查询用户手机号返回为空");
        UserPhoneNumberRes res;
        try {
            res = objectMapper.readValue(content, UserPhoneNumberRes.class);
        } catch (JsonProcessingException e) {
            throw new SystemException("转换json["+content+"]到UserPhoneNumberRes失败",e);
        }
        postProcessAccessToken(accessToken,client);
        return res;
    }

    private void postProcessAccessToken(AccessTokenRes accessToken, Integer client) {
        if(accessToken.nearExpire()){
            executorService.submit(()->getAccessToken(client,false));
        }
    }

    private AccessTokenRes getAccessToken(Integer client, boolean expired) {
        AccessTokenRes accessToken = this.accessToken.get(client);
        if(accessToken == null){
            synchronized (AccessTokenRes.class){
                accessToken = this.accessToken.get(client);
                if(accessToken == null){
                    accessToken = login(client);
                    this.accessToken.put(client, accessToken);
                }
            }
        } else {
            boolean update = false;
            if(expired){
                if(accessToken.expired()){
                    update = true;
                }
            } else {
                if(accessToken.nearExpire()){
                    update = true;
                }
            }
            if(update){
                synchronized (accessToken) {
                    AccessTokenRes newAccessToken = this.accessToken.get(client);
                    if(newAccessToken == accessToken){  // 这里必须是同一个对象
                        //还没有人更新,并且我已经拿到了锁
                        accessToken = login(client);
                        this.accessToken.put(client, accessToken);
                    }else{
                        //在我请求锁这段时间已经有人更新了
                        accessToken = newAccessToken;
                    }
                }
            }
        }
        return accessToken;
    }

    @Override
    public Code2SessionRes code2Session(String code, Integer client) {
        WxMiniProperties.WxMiniProgram program = wxMiniProperties.find(client);
        ErrorCode.PROPERTY_CHECK_ERROR.assertNotNull(program, "客户端类型","非法的客户端类型");
        String url = "https://api.weixin.qq.com/sns/jscode2session?appid={appid}&secret={secret}&js_code={code}&grant_type=authorization_code";
        String content = restTemplate.getForObject(url, String.class, program.getAppId(),program.getSecret(),code);
        if(StringUtils.isBlank(content)){
            throw new SystemException("code2Session调用返回内容为空");
        }
        Code2SessionRes res;
        try {
            res = objectMapper.readValue(content, Code2SessionRes.class);
        } catch (JsonProcessingException e) {
            throw new SystemException("转换json["+content+"]到Code2SessionRes失败",e);
        }
        return res;
    }

    @Override
    public void wxCode(HttpServletResponse response, WxQRCodeParam param) {
        String url = "https://api.weixin.qq.com/wxa/getwxacodeunlimit?access_token={ACCESS_TOKEN}";
        AccessTokenRes accessToken = getAccessToken(param.getClient(),true);
        BeanMap beanMap = BeanMap.create(param);
        Map<String,Object> map = new HashMap<>();
        beanMap.forEach((k,v)->map.put(String.valueOf(k),v));
        map.remove("envVersion");
        map.remove("client");
        map.put("env_version", param.getEnvVersion());
        map.put("check_path", false);
        ResponseEntity<byte[]> res = restTemplate.postForEntity(url,map,byte[].class,accessToken.getAccessToken());
        if(res.getStatusCode().is2xxSuccessful()){
            if(MediaType.APPLICATION_JSON.isCompatibleWith(res.getHeaders().getContentType())){
                String str = new String(Objects.requireNonNull(res.getBody()), StandardCharsets.UTF_8);
                try {
                    WxRes wxRes = objectMapper.readValue(str, WxRes.class);
                    ErrorCode.WX_INVOKER_ERROR.fail(String.format("errcode:%d,errmsg:%s",wxRes.getErrorCode(),wxRes.getErrorMessage()));
                } catch (JsonProcessingException e) {
                    throw new SystemException("转换json["+str+"]到WxRes失败",e);
                }
            }else{
                try(OutputStream os = response.getOutputStream()) {
                    response.setContentType(res.getHeaders().getFirst(HttpHeaders.CONTENT_TYPE));
                    response.setContentLengthLong(res.getHeaders().getContentLength());
                    os.write(Objects.requireNonNull(res.getBody()));
                } catch (IOException e) {
                    throw new SystemException("读取二维码内容失败",e);
                }
            }
        }
        postProcessAccessToken(accessToken,param.getClient());
    }

    private AccessTokenRes login(Integer client){
        WxMiniProperties.WxMiniProgram program = wxMiniProperties.find(client);
        ErrorCode.PROPERTY_CHECK_ERROR.assertNotNull(program,"客户端类型","非法的客户端类型");
        String url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid={appId}&secret={secret}";
        String content = restTemplate.getForObject(url,String.class,program.getAppId(),program.getSecret());
        if(StringUtils.isBlank(content)){
            throw new SystemException("客户端模式登录微信返回内容为空");
        }
        AccessTokenRes accessTokenRes;
        try {
            accessTokenRes = objectMapper.readValue(content, AccessTokenRes.class);
        } catch (JsonProcessingException e) {
            throw new SystemException("转换["+content+"]到AccessTokenRes失败",e);
        }
        AccessTokenRes res = accessTokenRes;
        ErrorCode.WX_INVOKER_ERROR.assertNotNull(res,"获取token返回空");
        ErrorCode.WX_INVOKER_ERROR.assertTrue(res.success(),()->new Object[]{String.format("errcode:%d,errmsg:%s",res.getErrorCode(),res.getErrorMessage())});
        res.setExpiresAt(new Date(new Date().getTime()+res.getExpiresIn()*1000));
        return res;
    }
}
